from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import datetime  # For datetime objects
import os.path  # To manage paths
import warnings
import pandas as pd
import backtrader as bt
import json

import sqlalchemy

import okex.v5.market_api as market
from strategies import macdStrategy
from tools.dingdingrobot import call_dingtalk
import time
from sqlalchemy import create_engine
warnings.filterwarnings("ignore")

test = True
def get_history_candles(marketAPI, instId, days=1, bar='4H'):
    """

    :param marketAPI:
    :param instId:
    :param days:
    :param bar:
    :return:
    """
    call_dingtalk("获取历史数据开始", 'text', test=test)
    # 获取当前时间的毫秒级时间戳
    now = datetime.datetime.now()
    timestamp_now = int(now.timestamp() * 1000)
    # 获取两个月之前的时刻并将其转换为毫秒级时间戳
    two_months_ago = now - datetime.timedelta(days=days)
    timestamp_two_months_ago = int(two_months_ago.timestamp() * 1000)
    history_candles = marketAPI.history_candles(instId, before=timestamp_two_months_ago,
                                                after=timestamp_now, bar=bar)
    time_obj = history_candles.iloc[-1, 0]
    timestamp = int(datetime.datetime.strptime(str(time_obj), '%Y-%m-%d %H:%M:%S').timestamp()*1000)

    old_timestamp = None
    while timestamp - timestamp_two_months_ago:
        time.sleep(2)
        try:
            more_history_candles = marketAPI.history_candles(instId, before=timestamp_two_months_ago,
                                                                after=timestamp, bar=bar)

        except:
            time_obj = history_candles.iloc[-1, 0]
            print("获取数据失败，时间为{}".format(str(time_obj)))
            continue
        if len(more_history_candles) == 0:
            time_obj = history_candles.iloc[-1, 0]
            print("获取数据结束，时间为{}".format(str(time_obj)))
            break
        history_candles = pd.concat([history_candles, more_history_candles])
        history_candles.reset_index(drop=True, inplace=True)
        time_obj = history_candles.iloc[-1, 0]
        print("当前获取的时间为{}".format(str(time_obj)))
        timestamp = int(datetime.datetime.strptime(str(time_obj), '%Y-%m-%d %H:%M:%S').timestamp()*1000)
        if old_timestamp == None:
            old_timestamp = timestamp
        else:
            if old_timestamp == timestamp:
                break
            else:
                old_timestamp = timestamp
    return history_candles

def get_history_candles_engine(marketAPI, instId, engine,days=1, bar='4H',table_name = "default",from_time_stamp = None,to_time_stamp = None):
    """

    :param marketAPI:
    :param instId:
    :param days:
    :param bar:
    :return:
    """
    call_dingtalk("获取历史数据开始", 'text', test=test)
    # 获取当前时间的毫秒级时间戳
    now = datetime.datetime.now()
    timestamp_now = int(now.timestamp() * 1000)
    from_time = None
    if from_time_stamp ==None:
        # 获取两个月之前的时刻并将其转换为毫秒级时间戳
        two_months_ago = now - datetime.timedelta(days=days)
        from_time = int(two_months_ago.timestamp() * 1000)
    else:
        from_time = from_time_stamp

    history_candles = marketAPI.history_candles(instId, before=from_time,
                                                after=timestamp_now, bar=bar)
    if len(history_candles) == 0:
        return history_candles
    print("写入数据库开始")
    history_candles.to_sql(table_name, con=engine, if_exists='append', index=True)
    print("写入数据库成功")
    time_obj = history_candles.iloc[-1, 0]
    timestamp = int(datetime.datetime.strptime(str(time_obj), '%Y-%m-%d %H:%M:%S').timestamp()*1000)

    old_timestamp = None
    while timestamp - from_time:
        time.sleep(2)
        try:
            more_history_candles = marketAPI.history_candles(instId, before=from_time,
                                                                after=timestamp, bar=bar)
            # 写入数据库
            print("写入数据库开始")
            more_history_candles.to_sql(table_name, con=engine, if_exists='append', index=True)
            print("写入数据库成功")
        except:
            time_obj = history_candles.iloc[-1, 0]
            print("获取数据失败，时间为{}".format(str(time_obj)))
            continue
        if len(more_history_candles) == 0:
            time_obj = history_candles.iloc[-1, 0]
            print("获取数据结束，时间为{}".format(str(time_obj)))
            break
        history_candles = pd.concat([history_candles, more_history_candles])
        history_candles.reset_index(drop=True, inplace=True)

        time_obj = history_candles.iloc[-1, 0]
        print("当前获取的时间为{}".format(str(time_obj)))
        timestamp = int(datetime.datetime.strptime(str(time_obj), '%Y-%m-%d %H:%M:%S').timestamp()*1000)
        if old_timestamp == None:
            old_timestamp = timestamp
        else:
            if old_timestamp == timestamp:
                break
            else:
                old_timestamp = timestamp
        print("total data length is {}".format(len(history_candles)))
    return history_candles


# 自定义csv
class My_PandasData(bt.feeds.PandasData):
    lines = ('signal',)

    params = (
        ('open', 0),
        ('high', 1),
        ('low', 2),
        ('close', 3),
        ('signal', 4),
        ('volume', -1),
        ('openinterest', -1),
    )


# Create a Stratey
class GeneralStrategy(bt.Strategy):
    params = (
        ('maperiod', 15),
    )

    def log(self, txt, dt=None):
        ''' Logging function fot this strategy'''
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        # Keep a reference to the "close" line in the data[0] dataseries
        self.dataclose = self.datas[0].close

        # To keep track of pending orders and buy price/commission
        self.order = None
        self.buyprice = None
        self.buyatr = None
        self.sellatr = None
        self.sellprice = None
        self.buycomm = None
        self.win_point = None  # 止盈位置
        self.loss_point = None  # 止损位置
        self.atrbase = 3  # atr止损基础
        self.win_loss_rate = 1.5  # 盈亏比
        self.change_rate = 0.04  # 价格浮动幅度
        # Add a MovingAverageSimple indicator
        self.sma = bt.indicators.SimpleMovingAverage(
            self.datas[0], period=self.params.maperiod)
        self.atr = bt.indicators.ATR(self.datas[0])
        # Indicators for the plotting show
        bt.indicators.ExponentialMovingAverage(self.datas[0], period=25)
        bt.indicators.WeightedMovingAverage(self.datas[0], period=25,
                                            subplot=True)
        bt.indicators.StochasticSlow(self.datas[0])
        bt.indicators.MACDHisto(self.datas[0])
        rsi = bt.indicators.RSI(self.datas[0])
        bt.indicators.SmoothedMovingAverage(rsi, period=10)
        bt.indicators.ATR(self.datas[0], plot=True)

    def notify_order(self, order):
        if order.status in [order.Submitted, order.Accepted]:
            # Buy/Sell order submitted/accepted to/by broker - Nothing to do
            return

        # Check if an order has been completed
        # Attention: broker could reject order if not enough cash
        if order.status in [order.Completed]:
            if order.isbuy():
                self.log(
                    'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                    (order.executed.price,
                     order.executed.value,
                     order.executed.comm))

                self.buyprice = order.executed.price
                self.buycomm = order.executed.comm
                self.win_point = self.buyprice + self.win_loss_rate * self.atr * self.atrbase
                self.loss_point = self.buyprice - self.atr * self.atrbase
            else:  # Sell
                self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
                         (order.executed.price,
                          order.executed.value,
                          order.executed.comm))
                self.sellprice = order.executed.price
                self.win_point = self.sellprice - self.win_loss_rate * self.atr * self.atrbase
                self.loss_point = self.sellprice + self.atr * self.atrbase
            self.bar_executed = len(self)

        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            self.log('Order Canceled/Margin/Rejected')

        # Write down: no pending order
        self.order = None

    def notify_trade(self, trade):
        if not trade.isclosed:
            return

        self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
                 (trade.pnl, trade.pnlcomm))

    def next(self):
        # print(self.data.getlinealiases())
        # print(self.data.lines.datetime[0])
        # Simply log the closing price of the series from the reference
        self.log('Close, %.2f' % self.dataclose[0])
        # 输出持仓
        self.log('持仓 %.2f' % self.position.size)
        # Check if an order is pending ... if yes, we cannot send a 2nd one
        if self.order:
            return

        # Check if we are in the market
        if self.position.size == 0:
            # Not yet ... we MIGHT BUY if ...
            if self.data.lines.signal[0] == 1:
                # if self.dataclose[0] > self.sma[0]:
                # BUY, BUY, BUY!!! (with all possible default parameters)
                self.log('BUY CREATE, %.2f' % self.dataclose[0])
                # Keep track of the created order to avoid a 2nd order
                self.order = self.buy()
                # self.buyprice = self.dataclose[0]

            if self.data.lines.signal[0] == 2:
                # if self.dataclose[0] < self.sma[0]:
                # SELL, SELL, SELL!!! (with all possible default parameters)
                self.log('SELL CREATE, %.2f' % self.dataclose[0])

                # Keep track of the created order to avoid a 2nd order
                self.order_target_percent(data=self.datas[0], target=1.0)
                self.order = self.sell()
                # self.sellprice = self.dataclose[0]
            # print(self.data.lines.close[0])
        # 持有正仓位
        elif self.position.size > 0:
            if self.dataclose[0] >= self.win_point:
                self.log('SELL CREATE 平多赚了, %.2f' % self.dataclose[0])
                self.order = self.sell()
            elif self.dataclose[0] <= self.loss_point:
                self.log('SELL CREATE 平多止损, %.2f' % self.dataclose[0])
                self.order = self.sell()
        # 持有空单负仓位
        elif self.position.size < 0:
            if self.dataclose[0] <= self.win_point:
                self.log('BUY CREATE 平空赚了, %.2f' % self.dataclose[0])
                self.order = self.buy()
            elif self.dataclose[0] >= self.loss_point:
                self.log('BUY CREATE 平空止损, %.2f' % self.dataclose[0])
                self.order = self.buy()



if __name__ == '__main__':

    base_dir = r"../"

    with open(os.path.join(base_dir, "passwords", "passwords.json"), 'r') as f:
        data = json.load(f)

    if test:
        # 模拟盘api
        api_key = data[0]['api_key']
        secret_key = data[0]['secret_key']
        passphrase = data[0]['passphrase']
    else:
        # 实盘API
        api_key = data[1]['api_key']
        secret_key = data[1]['secret_key']
        passphrase = data[1]['passphrase']

    obj = dict([('api_key', api_key), ('secret_key', secret_key), ('passphrase', passphrase)])

    api_key = obj['api_key']
    secret_key = obj['secret_key']
    passphrase = obj['passphrase']

    instId = 'BTC-USDT-SWAP'
    # 市场行情API
    marketAPI = market.MarketAPI(api_key, secret_key, passphrase, use_server_time=False, test=test)
    marketAPI.set_api_url("https://www.okx.com")
    days = 365
    # 该k线用于模拟实时实盘交易
    engine = create_engine('mysql+pymysql://root:321ewqdsacxz@localhost:3306/BTC_HISTORY')
    # 编写SQL查询语句
    table_name = 'btc_usdt_swap_5_year'
    sql_query = "SELECT * FROM "+table_name
    if sqlalchemy.inspect(engine).has_table(table_name):
        real_time_history_df = pd.read_sql_query(sql_query, engine)
        # 删除重复行
        real_time_history_df.drop_duplicates(subset=['ts'], keep='first', inplace=True)
        # 根据ts排序
        real_time_history_df.sort_values(by='ts', ascending=False,inplace=True)
        latest_time = real_time_history_df.iloc[0,:]['ts']
        oldest_time = real_time_history_df.iloc[-1,:]['ts']
        from_time_stamp = int(datetime.datetime.strptime(str(latest_time), '%Y-%m-%d %H:%M:%S').timestamp()*1000)
        get_history_candles_engine(marketAPI,instId,engine,table_name=table_name,bar='1m',from_time_stamp=from_time_stamp)
    real_time_history_df = pd.read_sql_query(sql_query,engine)
    # 删除重复行
    real_time_history_df.drop_duplicates(subset=['ts'], keep='first', inplace=True)
    # 根据ts排序
    real_time_history_df.sort_values(by='ts', ascending=False, inplace=True)

    real_time_history_df = real_time_history_df.loc[:,['ts','o','h','l','c']]
    # 信号df
    # signal_df = get_history_candles(marketAPI,instId,days=days,bar = '4H')
    bar = '4H'
    table_name = 'btc_usdt_swap_macd_signal'
    sql_query = "SELECT * FROM " + table_name
    if sqlalchemy.inspect(engine).has_table(table_name):
        real_time_history_4h = pd.read_sql_query(sql_query, engine)
        # 删除重复行
        real_time_history_4h.drop_duplicates(subset=['ts'], keep='first', inplace=True)
        # 根据ts排序
        real_time_history_4h.sort_values(by='ts', ascending=False,inplace=True)
        latest_time = real_time_history_4h.iloc[0,:]['ts']
        oldest_time = real_time_history_4h.iloc[-1,:]['ts']
        from_time_stamp = int(datetime.datetime.strptime(str(latest_time), '%Y-%m-%d %H:%M:%S').timestamp()*1000)
        get_history_candles_engine(marketAPI,instId,engine,table_name=table_name,bar=bar,from_time_stamp=from_time_stamp)
    else:
        # 初始化获取数据并写入数据库
        days = 365 * 5  # 先写n年的数据
        # 该k线用于模拟实时实盘交易
        real_time_history_4h = get_history_candles_engine(marketAPI, instId, engine, table_name=table_name, days=days,
                                                   bar=bar)
    signal_df = pd.read_sql_query(sql_query,engine)
    signal_df = macdStrategy.macdstartegy(signal_df)
    merged_df = pd.merge(real_time_history_df, signal_df.loc[:, ['ts', 'signal']], on='ts', how='left')
    merged_df['signal'].fillna(0,inplace=True)
    merged_df = merged_df[:100000]
    print("回测开始")
    # Create a cerebro entity
    cerebro = bt.Cerebro()

    # Add a strategy
    cerebro.addstrategy(GeneralStrategy)
    cerebro.addanalyzer(bt.analyzers.AnnualReturn, _name='AnnualReturn')
    cerebro.addanalyzer(bt.analyzers.SharpeRatio, riskfreerate=0.03, annualize=True, _name='SharpeRatio')

    merged_df.set_index('ts',inplace=True)
    # merged_df.drop('index', axis=1, inplace=True)
    merged_df = merged_df.loc[:, ['o', 'h', 'l', 'c','signal']]
    merged_df.sort_index(ascending=True, inplace=True)
    data_feed = My_PandasData(dataname=merged_df)

    cerebro.adddata(data_feed)
    # Set our desired cash start
    start_cash = 10000000.0
    cerebro.broker.setcash(start_cash)

    # Add a FixedSize sizer according to the stake
    cerebro.addsizer(bt.sizers.PercentSizer, percents=90)

    # Set the commission
    cerebro.broker.setcommission(commission=0.0)

    # Print out the starting conditions
    print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())

    # Run over everything
    cerebro.run()

    # Print out the final result
    print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())

    # Print策略的收益率
    print('策略总体的收益率为:{}%'.format((cerebro.broker.getvalue() - start_cash) / start_cash * 100))
    #
    # annual_return = GeneralStrategy.analyzers.AnnualReturn.get_analysis()
    # print("年化收益率：", annual_return)
    #
    # sharpe_ratio = GeneralStrategy.analyzers.SharpeRatio.get_analysis()['sharperatio']
    # print("夏普比率：", sharpe_ratio)

    # Plot the result
    cerebro.plot(volume=False)
